package com.gee.spring.cloud.feign.flow.work;

import com.gee.spring.cloud.feign.flow.binding.annotation.FlowArg;
import com.gee.spring.cloud.feign.flow.binding.annotation.TempVar;
import com.gee.spring.cloud.feign.flow.execute.ExecuteContext;
import com.gee.spring.cloud.feign.flow.execute.WorkExecuteProperties;
import com.gee.spring.cloud.feign.flow.handler.WorkHandler;
import com.gee.spring.cloud.feign.flow.result.CheckResult;
import com.gee.spring.cloud.feign.flow.result.ExecuteState;
import com.gee.spring.cloud.feign.flow.result.WorkResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.function.BiConsumer;

/**
 * desc:
 *   V: 线程组启动时, 使用的统一参数
 *   P: 本单元使用的参数
 *   R: 本单元返回类型
 * @author gee wrote on  2021-01-14 19:03:01
 */
public abstract class AbstractWork<R> implements BiConsumer<ExecuteContext, Throwable>, Work<R> {

    private final Logger logger = LoggerFactory.getLogger(AbstractWork.class);

    private WorkHandler workHandler;

    private WorkExecuteProperties<R> workExecuteProperties = new WorkExecuteProperties<>();

    private Object target;

    private Method method;

    private FlowArg[] argsAnnotations;

    private TempVar[] tempVars;

    public void work(ExecuteContext executeContext) {
        R r = null;
        Exception exception = null;
        try {
            CheckResult checkResult = workExecuteProperties.getCheckResult();
            ExecuteState expectState = checkResult.getExpectState();
            if (expectState == ExecuteState.EXCEPTIONAL) {
                workHandler.handleBeforeBegin(this, executeContext);
                exception = checkResult.getException();
            } else if (expectState == ExecuteState.WORKING) {
                Object[] args = executeContext.getArgs(argsAnnotations);
                workHandler.handleBeforeBegin(this, executeContext);
                r = process(target, method, args);
            }
        } catch (Exception e) {
            exception = e;
            logger.error("client {} execute exceptionally ", getId(),e);
        }
        setResult(executeContext, r, exception);
        workHandler.handleAfterFinish(this, executeContext);
    }

    public void setResult(ExecuteContext executeContext, R r, Exception exception) {
        WorkResult workResult = executeContext.getWorkResult(this.getId());
        if (workResult.getExecuteState().get() == ExecuteState.WORKING.getCode()
             && executeContext.getGlobalExecuteState().get() == ExecuteState.WORKING.getCode()
             && exception == null){
            workResult.setResult(r);
        }else if (executeContext.getGlobalExecuteState().get() == ExecuteState.EXCEPTIONAL.getCode()){
            workResult.setException(executeContext.getGlobalException());
            workResult.setExceptionMsg(executeContext.getGlobalException().getMessage());
        }else if (workResult.getExecuteState().get() == ExecuteState.WORKING.getCode()){
            workResult.setException(exception);
            workResult.setExceptionMsg(exception.getMessage());
        }
    }

    @Override
    public void accept(ExecuteContext executeContext, Throwable throwable) {
        workExecuteProperties.setSelfThread(Thread.currentThread());
        if (throwable == null) {
            this.work(executeContext);
        }
    }

    public void initExecuteContext(ExecuteContext executeContext) {
        if (! executeContext.getWorkResultMap().containsKey(this.getId())){
            WorkResult<R> workResult = new WorkResult<>();
            executeContext.getWorkResultMap().put(this.getId(), workResult);
            workExecuteProperties.getNextWorks().forEach((id, nextWorkWrapper) ->
                    nextWorkWrapper.getWork().initExecuteContext(executeContext)
            );
        }
    }

    /**
     *  work真正执行的方法
     * @return R
     * @throws Exception Exception
     */

    @Override
    public abstract R process(Object target, Method method, Object[] args) throws Exception;

    /**
     *  返回work的id
     * @return id
     */
    public abstract String getId();

    public WorkExecuteProperties<R> getWorkExecuteProperties() {
        return workExecuteProperties;
    }

    public WorkHandler getWorkHandler() {
        return workHandler;
    }

    public void setWorkHandler(WorkHandler workHandler) {
        this.workHandler = workHandler;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public void setArgsAnnotations(FlowArg[] argsAnnotations) {
        this.argsAnnotations = argsAnnotations;
    }

    public TempVar[] getTempVars() {
        return tempVars;
    }

    public void setTempVars(TempVar[] tempVars) {
        this.tempVars = tempVars;
    }
}
